package node

import (
	"context"
	"fmt"
	"sort"
	"sync"
	"time"

	"github.com/libp2p/go-libp2p-core/host"
	"hundsun.com/hsl/hschain/p2p/common"

	"github.com/libp2p/go-libp2p-core/metrics"
	"github.com/libp2p/go-libp2p-core/network"
	"github.com/libp2p/go-libp2p-core/peer"
	kb "github.com/libp2p/go-libp2p-kbucket"
	"github.com/multiformats/go-multiaddr"
	"hundsun.com/hsl/hschain/common/log/logging"
	pbp2p "hundsun.com/hsl/hschain/protos/p2p"
)

const (
	maxBounds    = 20 //最大连接数包含连接被连接
	maxOutBounds = 10 //对外连接的最大节点数量
)

// ConnManager p2p connection manager
type ConnManager struct {
	logging.Log
	ctx              context.Context
	peerStoreLAN     sync.Map
	host             host.Host
	bandwidthCounter *metrics.BandwidthCounter
	routingTable     *kb.RoutingTable
	cfg              *common.P2pConfig
}

// NewConnManager new connection manager
func NewConnManager(ctx context.Context, host host.Host, rt *kb.RoutingTable, bw *metrics.BandwidthCounter, conf *common.P2pConfig, logger logging.Log) *ConnManager {
	connM := &ConnManager{}
	connM.Log = logger
	connM.ctx = ctx
	connM.host = host
	connM.routingTable = rt
	connM.bandwidthCounter = bw
	connM.cfg = conf

	return connM
}

// BandTrackerByProtocol returns all protocols band info
func (s *ConnManager) BandTrackerByProtocol() *pbp2p.NetConnStatsList {
	if s.bandwidthCounter == nil {
		return nil
	}

	bandprotocols := s.bandwidthCounter.GetBandwidthByProtocol()
	type tmpStats struct {
		ID        string
		ratetotal float64
		ratein    float64
		rateout   float64
	}
	var stats []*tmpStats
	for id, stat := range bandprotocols {
		if id == "" || stat.RateIn+stat.RateOut == 0 {
			continue
		}
		s := &tmpStats{
			ID:        string(id),
			ratetotal: stat.RateIn + stat.RateOut,
			ratein:    stat.RateIn,
			rateout:   stat.RateOut,
		}
		stats = append(stats, s)
	}

	if len(stats) > 1 {
		sort.Slice(stats, func(i, j int) bool {
			return stats[i].ratetotal < stats[j].ratetotal
		})
	}

	slist := &pbp2p.NetConnStatsList{}
	for _, s := range stats {
		ps := &pbp2p.NetConnStats{
			Protocol:  s.ID,
			Ratetotal: RateFmt(s.ratetotal),
			Ratein:    RateFmt(s.ratein),
			Rateout:   RateFmt(s.rateout),
		}
		slist.List = append(slist.List, ps)
	}

	return slist

}

//Start MonitorConns monitor conns
func (s *ConnManager) Start() {
	ticker1 := time.NewTicker(time.Minute)
	ticker2 := time.NewTicker(time.Minute * 2)
	s.printMonitorInfo()
	s.procConnections()
	for {
		select {
		case <-s.ctx.Done():
			return
		case <-ticker1.C:
			s.printMonitorInfo()
		case <-ticker2.C:
			s.procConnections()
		}
	}
}

func (s *ConnManager) printMonitorInfo() {
	var LatencyInfo = fmt.Sprintln("--------------TimeDelay--------------------")
	peers := s.FetchConnPeers()
	bandByPeer := s.bandwidthCounter.GetBandwidthByPeer()
	var trackerInfo = fmt.Sprintln("------------BandTracker--------------")
	for _, pid := range peers {
		//统计每个节点的时延,统计最多MaxBounds个
		tduration := s.host.Peerstore().LatencyEWMA(pid)
		if tduration == 0 {
			continue
		}
		LatencyInfo += fmt.Sprintln("PeerID:", pid.Pretty(), "LatencyEWMA:", tduration)
		if stat, ok := bandByPeer[pid]; ok {
			trackerInfo += fmt.Sprintf("PeerID:%s,RateIn:%f bytes/s,RateOut:%f bytes/s,totalIn:%d bytes,totalOut:%d\n",
				pid,
				stat.RateIn,
				stat.RateOut,
				stat.TotalIn,
				stat.TotalOut)
		}
		//protocols rate
		s.Log.Debug(LatencyInfo)

		insize, outsize := s.BoundSize()
		trackerInfo += fmt.Sprintln("peerstoreNum:", len(s.host.Peerstore().Peers()), ",conn num:", insize+outsize, "inbound num", insize, "outbound num", outsize,
			"dht size", s.routingTable.Size())
		trackerInfo += fmt.Sprintln("-------------------------------------")
		s.Log.Debug(trackerInfo)
	}
}

func (s *ConnManager) procConnections() {
	_, outBoundSize := s.BoundSize()
	if outBoundSize > maxOutBounds || s.Size() > maxBounds {
		return
	}

	for _, node := range s.cfg.BootstrapPeers {
		info := genAddrInfo(node)
		_ = s.host.Connect(context.Background(), *info)
	}

	//受保护的连接节点,需要长期保持连接
	for _, node := range s.cfg.ProtectPeers {
		info := genAddrInfo(node)
		s.host.Connect(context.Background(), *info)
	}
}

// AddLANStore add LAN store by peer info
func (s *ConnManager) AddLANStore(pr *peer.AddrInfo) {
	s.peerStoreLAN.Store(pr.ID.Pretty(), pr)
}

// IsAddLANStore check is LAN store by id
func (s *ConnManager) IsAddLANStore(pid peer.ID) bool {
	_, ok := s.peerStoreLAN.Load(pid.Pretty())
	return ok
}

// Delete delete peer by id
func (s *ConnManager) Delete(pid peer.ID) {
	_ = s.host.Network().ClosePeer(pid)
	s.routingTable.Remove(pid)
}

// FetchNearestPeers fetch nearest peer ids
func (s *ConnManager) FetchNearestPeers(count int) []peer.ID {
	if s.routingTable == nil {
		return nil
	}
	return s.routingTable.NearestPeers(kb.ConvertPeerID(s.host.ID()), count)
}

// Size connections size
func (s *ConnManager) Size() int {
	return len(s.host.Network().Conns())
}

//Filter peerID过滤方法
type Filter func(peer.ID) bool

// FetchConnPeers 获取连接的Peer's ID 这个连接包含被连接的peer以及主动连接的peer.
func (s *ConnManager) FetchConnPeers(filters ...Filter) []peer.ID {
	var peers = make(map[string]peer.ID)

	var conns []network.Conn
	conns = append(conns, s.host.Network().Conns()...)

	//if len(conns) > 1 {
	//	// 根据连接时长进行排序
	//	sort.Slice(conns, func(i, j int) bool {
	//		return conns[i].Stat().Opened.After(conns[j].Stat().Opened)
	//	})
	//}

	for _, conn := range conns {
		//filter 过滤
		for _, filter := range filters {
			if !filter(conn.RemotePeer()) {
				break
			}
		}
		peers[conn.RemotePeer().Pretty()] = conn.RemotePeer()
		if len(peers) >= maxBounds {
			break
		}
	}
	return convertMapToArr(peers)
}

func convertMapToArr(in map[string]peer.ID) []peer.ID {
	var pids []peer.ID
	for _, id := range in {
		pids = append(pids, id)
	}
	return pids
}

// CheckDirection 检查连接的节点ID是被连接的还是主动连接的
func (s *ConnManager) CheckDirection(pid peer.ID) network.Direction {
	for _, conn := range s.host.Network().ConnsToPeer(pid) {
		return conn.Stat().Direction
	}
	return network.DirUnknown
}

// OutBounds get out bounds conn peers
func (s *ConnManager) OutBounds() []peer.ID {
	var peers []peer.ID
	for _, con := range s.host.Network().Conns() {
		if con.Stat().Direction == network.DirOutbound {
			peers = append(peers, con.RemotePeer())
		}
	}
	return peers
}

// InBounds get in bounds conn peers
func (s *ConnManager) InBounds() []peer.ID {
	var peers []peer.ID
	for _, con := range s.host.Network().Conns() {
		if con.Stat().Direction == network.DirInbound {
			peers = append(peers, con.RemotePeer())
		}
	}
	return peers
}

// BoundSize get in out conn bound size
func (s *ConnManager) BoundSize() (insize int, outsize int) {
	for _, con := range s.host.Network().Conns() {
		if con.Stat().Direction == network.DirOutbound {
			outsize++
		}
		if con.Stat().Direction == network.DirInbound {
			insize++
		}
	}
	return insize, outsize
}

// GetNetRate get rateinfo
func (s *ConnManager) GetNetRate() metrics.Stats {
	return s.bandwidthCounter.GetBandwidthTotals()
}

//RateFmt RateFmt
func RateFmt(ratebytes float64) string {
	kbytes := ratebytes / 1024
	rate := fmt.Sprintf("%.3f KB/s", kbytes)

	if kbytes/1024 > 0.1 {
		rate = fmt.Sprintf("%.3f MB/s", kbytes/1024)
	}

	return rate
}

func genAddrInfo(addr string) *peer.AddrInfo {
	mAddr, err := multiaddr.NewMultiaddr(addr)
	if err != nil {
		return nil
	}
	peerInfo, err := peer.AddrInfoFromP2pAddr(mAddr)
	if err != nil {
		return nil
	}
	return peerInfo
}
